using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using OhioState.AssetManaged;
using OhioState.Graphics.Proxy;
using OhioState.Math;


namespace OhioState.Controls
{
    //
    // GradientCtrl - a control that displays a transfer function
    //  and allows the user to modify it
    //
    public partial class GradientControl : UserControl
    {
        //
        // Transfer function that is currently being modified
        //
        private TransferFunction transFunc;
        // This keeps track of what the control should look like when nothin is selected
        private TransferFunction blankFunc;
        
        //
        // None selected flag
        //
        bool noneSelected;

        //
        // Event for keeping track of subscriptions to transfer
        //  function update events
        //
        IAssetManagedHandler updateEvent;

        //
        // Window size, Bar size, and arrow drawing parameters
        //
        private int width, height, left, top;
        private int arrow_w, arrow_h;
        private Color arrow_color;

        //
        // Parameters for moving arrows
        //
        private Knot move_arrow;
        private int mov_x, mov_y;
        private bool moving;

        //
        // Other controls that are needed
        //  AlphaPickerDialog
        //
        private AlphaPickerDialog alphaPickerDialog;

        public GradientControl()
        {
            InitializeComponent();

            //
            // Create a blank transfer function that will be used whenever
            //  nothing is selected
            //
            blankFunc = new TransferFunction("None Selected", false);
            blankFunc.AddAlphaKnot(new AlphaKnot(0.0f, 0.0f));
            blankFunc.AddAlphaKnot(new AlphaKnot(1.0f, 0.0f));
            blankFunc.AddColorKnot(new ColorKnot(0.0f, 0.5f, 0.5f, 0.5f));
            blankFunc.AddColorKnot(new ColorKnot(1.0f, 0.5f, 0.5f, 0.5f));

            transFunc = blankFunc;
            noneSelected = true;

            //
            // Initialize the alpha picker dialog
            //
            alphaPickerDialog = new AlphaPickerDialog();

            //
            // Set default size and drawing parameters
            //
            height = 20; // bar height
            Padding = 5; // padding around bars
            arrow_w = 5; // width of an arrow (also controls the height of the point)
            arrow_h = 9; // height of the box part of the arrow
            arrow_color = Color.White; // background color of the arrows

            //
            // Initialize arrow moving parameters
            //
            moving = false;
            move_arrow = null;
        }

        //
        // Functions to change the transfer function that this control is modifying
        // I originally had these 2 function as a public Property but the VS Designer
        //  kept complaining to me so I changed it
        //
        public TransferFunction GetTransFunc()
        {
            return transFunc;
        }

        public void ControlNewTransFunc(TransferFunction transferFunction)
        {
            //
            // Unsubscribe from the update event of the old transfer function
            // Subscribe to the update event of the new transfer function
            //
            transFunc.RemoveChangingEventHandler(updateEvent);
            transFunc.RemoveChangedEventHandler(updateEvent);

            // check if the new transfer function is not blank
            if (transferFunction != null)
            {
                transFunc = transferFunction;
                updateEvent = new IAssetManagedHandler(transFunc_UpdatedEvent);
                transFunc.AddChangingEventHandler(updateEvent);
                transFunc.AddChangedEventHandler(updateEvent);
                noneSelected = false;
            }
            else
            {
                //
                // switch to blank function and set none selected flag
                //
                transFunc = blankFunc;
                noneSelected = true;
            }

            this.Invalidate();
        }

        //
        // Event handler triggered when the transfer function is updated
        //
        public void transFunc_UpdatedEvent(IAssetManaged obj)
        {
            this.Invalidate();
        }

        #region Size and Drawing Properties
        //
        // Properties for setting size and drawing parameters
        //
        // Padding controls the space around the bars
        //  It is also used when resizing to reset the padding based on the new borders
        //
        public new int Padding
        {
            get { return top;  }
            set
            {
                top = value;
                left = value + arrow_w - 2;
                width = this.Width - 2 * left - 4;
                this.Invalidate();
            }
        }

        //
        // Set the height of the color and alpha bars
        //
        public int BarHeight
        {
            get { return height; }
            set
            {
                if (value < 1)
                    height = 1;
                else
                    height = value;
                Padding = Padding;
            }
        }

        //
        // Set the height of the box portion of the arrow controls
        //
        public int ArrowHeight
        {
            get { return arrow_h; }
            set
            {
                if (value < 0)
                    arrow_h = 0;
                else
                    arrow_h = value;
                Padding = Padding;
            }
        }

        //
        // Set the width of the box portion of the arrow controls
        //  as well as height of the point of the arrow
        //
        public int ArrowWidth
        {
            get { return arrow_w; }
            set
            {
                if (value < 2)
                    arrow_w = 2;
                else
                    arrow_w = value;
                Padding = Padding;
            }
        }

        //
        // Set the background color of the arrows when drawn
        //
        public Color ArrowColor
        {
            get { return arrow_color;  }
            set
            {
                arrow_color = value;
                Padding = Padding;
            }
        }
        #endregion

        #region Drawing Events
        //
        // Set the control to redraw when resized
        //
        private void GradientCtrl_Load(object sender, EventArgs e)
        {
            this.ResizeRedraw = true;
        }

        //
        // Override the OnPaintBackground function
        // This prevents flicker when drawing because Windows will not try to fight with
        //  the drawing that I perform on the panel
        //
        protected override void OnPaintBackground(System.Windows.Forms.PaintEventArgs pevent)
        {
            // Do nothing.
        }

        //
        // On resize auto adjust the height of the bars.  the other sizes will be automatically updated.
        //
        private void GradientCtrl_Resize(object sender, EventArgs e)
        {
            //Padding = Padding;
            BarHeight = Height / 2 - 4 - ArrowHeight - ArrowWidth - Padding;
        }

        //
        // Paint event - draw everything on screen:
        //  the background, the bar borders, the gradient bars, and the arrows
        //
        private void GradientCtrl_Paint(object sender, PaintEventArgs e)
        {
            int arr = arrow_h + arrow_w + 1;
            int i;

            // Double buffer setup
            System.Drawing.Graphics g;
            Bitmap drawing = new Bitmap(this.Width, this.Height, e.Graphics);
            g = System.Drawing.Graphics.FromImage(drawing);
            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighSpeed;
            g.FillRectangle(new SolidBrush(this.BackColor), new Rectangle(new Point(0, 0), this.ClientSize));

            // Draw Gradient
            Spline spline = transFunc.MergeSplines();
            DrawGradient(g, spline);

            // Draw Arrows
            ColorKnot k;

            for (i = 0; i < transFunc.ColorCount(); i++)
            {
                k = (ColorKnot)transFunc.GetColorKnot(i);
                DrawColorArrow(g, k);
            }

            for (i = 0; i < transFunc.AlphaCount(); i++)
            {
                k = (ColorKnot)transFunc.GetAlphaKnot(i);
                DrawAlphaArrow(g, k);
            }

            // Double buffer swap
            e.Graphics.DrawImageUnscaled(drawing, 0, 0);
            g.Dispose();
        }
        #endregion

        #region Drawing Helper functions
        //
        // Draw both the top alpha gradient bar and the bottom true color gradient bar
        // Use a checkerboard background on the alpha bar
        // Using the merged spline from the transfer function, loop through all knots and draw the sections
        //  in between them using the linear gradient brush
        //
        private void DrawGradient(System.Drawing.Graphics g, Spline spline)
        {
            int arr = arrow_h + arrow_w + 1;

            // Outline
            Pen p = new Pen(Color.Black, 1);
            g.DrawRectangle(p, left, top + arr, width + 3, height + 3); // top bar
            g.DrawRectangle(p, left, top + height + 3 + arr, width + 3, height + 3); // bottom bar

            // Checker Board
            HatchBrush checker = new HatchBrush(HatchStyle.LargeCheckerBoard, Color.Gray, Color.White);
            g.FillRectangle(checker, left + 2, top + 2 + arr, width, height);

            // Draw Gradient
            for (int i = 0; i < spline.Count-1; i++)
            {
                ColorKnot knot_a = (ColorKnot)spline[i];
                ColorKnot knot_b = (ColorKnot)spline[i+1];

                Point a = new Point((int)(width * knot_a.Pos) + left + 1, 0);
                Point b = new Point((int)(width * knot_b.Pos) + left + 2, 0);
                LinearGradientBrush alpha_brush = new LinearGradientBrush(a, b,
                    knot_a.Color.Color, knot_b.Color.Color);
                LinearGradientBrush solid_brush = new LinearGradientBrush(a, b,
                    knot_a.Color.TrueColor, knot_b.Color.TrueColor);
                g.FillRectangle(alpha_brush, a.X + 1, top + 2 + arr, b.X - a.X - 1, height); // top bar
                g.FillRectangle(solid_brush, a.X + 1, top + height + 5 + arr, b.X - a.X - 1, height); // bottom bar
            }
        }

        //
        // Create a graphics path for the shape of a color arrow.
        // This will be used to fill in and to draw the outline of the color arrows
        // All based on the parameters for arrow size
        //
        private GraphicsPath ColorArrowPath(ColorKnot k)
        {
            int pos = (int)(k.Pos * (width - 1)) + left + 2;
            int arr = arrow_h + arrow_w + 1;

            Point[] arrowPts =
			{
				new Point(pos-arrow_w, 2*height+7+top+arrow_w+arr),
				new Point(pos, 2*height+7+top+arr),
				new Point(pos+arrow_w, 2*height+7+top+arrow_w+arr),
				new Point(pos+arrow_w, 2*height+7+top+arrow_w+arrow_h+arr),
				new Point(pos-arrow_w, 2*height+7+top+arrow_w+arrow_h+arr)
			};

            //
            // This code was for showing that an arrow was locked and unmovable.
            // We no longer have unmovable points so this is no longer needed
            //
            /*
            Point[] lockedPts =
            {
                new Point(pos-arrow_w+1, 2*height+6+top+arrow_w+arr),
                new Point(pos, 2*height+7+top+arr),
                new Point(pos+arrow_w-1, 2*height+6+top+arrow_w+arr),
                new Point(pos+arrow_w-1, 2*height+5+top+arrow_w+arrow_h+arr),
                new Point(pos-arrow_w+1, 2*height+5+top+arrow_w+arrow_h+arr)
            };*/

            GraphicsPath path = new GraphicsPath();
            path.AddPolygon(arrowPts);

            return path;
        }

        //
        // Create a graphics path for the shape of an alpha arrow.
        // This will be used to fill in and to draw the outline of the alpha arrows
        // All based on the parameters for arrow size
        //
        private GraphicsPath AlphaArrowPath(ColorKnot k)
        {
            int pos = (int)(k.Pos * (width - 1)) + left + 2;

            Point[] arrowPts =
			{
				new Point(pos-arrow_w, top+arrow_h),
				new Point(pos, top+arrow_w+arrow_h),
				new Point(pos+arrow_w, top+arrow_h),
				new Point(pos+arrow_w, top),
				new Point(pos-arrow_w, top)
			};

            GraphicsPath path = new GraphicsPath();
            path.AddPolygon(arrowPts);

            return path;
        }

        //
        // Draw color and alpha arrow functions
        // These probably could be combined into one function if I thought about it a bit more
        //  but the reason they aren't one function right now is because the top of the arrow must
        //  be colored black when the arrow is selected, and the shape of that is different for color
        //  and alpha arrows
        //
        private void DrawColorArrow(System.Drawing.Graphics g, ColorKnot k)
        {
            SolidBrush b = new SolidBrush(k.Color.TrueColor);
            int pos = (int)(k.Pos * (width - 1)) + left + 2;
            int arr = arrow_h + arrow_w + 1;

            GraphicsPath path = ColorArrowPath(k);
            Pen p = new Pen(Color.Black, 1);
            g.FillPath(new SolidBrush(arrow_color), path);
            g.FillRectangle(b, pos - arrow_w + 2, 2 * height + top + arrow_w + 8 + arr, 2 * arrow_w - 3, arrow_h - 2);
            //g.FillRectangle(b, pos-arrow_w+3, 2*height+top+arrow_w+8+arr, 2*arrow_w-5, arrow_h-4);

            // Color in top if selected
            if (k == move_arrow)
            {
                Point[] arrowTop =
				{
					new Point(pos-arrow_w, 2*height+7+top+arrow_w+arr),
					new Point(pos, 2*height+7+top+arr),
					new Point(pos+arrow_w, 2*height+7+top+arrow_w+arr)
				};

                GraphicsPath arrow_top = new GraphicsPath();
                arrow_top.AddPolygon(arrowTop);
                g.FillPath(Brushes.Black, arrow_top);
            }

            // Draw arrow outline
            g.DrawPath(p, path);
        }

        private void DrawAlphaArrow(System.Drawing.Graphics g, ColorKnot k)
        {
            SolidBrush b = new SolidBrush(k.Color.TrueColor);
            int pos = (int)(k.Pos * (width - 1)) + left + 2;

            GraphicsPath path = AlphaArrowPath(k);
            Pen p = new Pen(Color.Black, 1);
            g.FillPath(new SolidBrush(arrow_color), path);
            g.FillRectangle(b, pos - arrow_w + 2, top + 2, 2 * arrow_w - 3, arrow_h - 2);
            //g.FillRectangle(b, pos-arrow_w+3, top+3, 2*arrow_w-5, arrow_h-4);

            // Color in top if selected
            if (k == move_arrow)
            {
                Point[] arrowTop =
				{
					new Point(pos-arrow_w+1, top+1+arrow_h),
					new Point(pos, top+1+arrow_w+arrow_h),
					new Point(pos+arrow_w, top+1+arrow_h)
				};

                GraphicsPath arrow_top = new GraphicsPath();
                arrow_top.AddPolygon(arrowTop);
                g.FillPath(Brushes.Black, arrow_top);
            }

            // Draw arrow outline
            g.DrawPath(p, path);
        }
        #endregion

        #region Context Menus
        //
        // This code handles the context menus
        //  They all have simple actions such as adding knots to both splines,
        //   deleting knots from both splines, modifying knots from each spline,
        //   and changing the drawing mode to either RGB or HSV space
        //
        private void colorArrowMenuSelect_Click(object sender, EventArgs e)
        {
            if (!noneSelected)
            {
                if (move_arrow != null)
                {
                    if (move_arrow.GetType() == typeof(AlphaKnot))
                    {
                        alphaPickerDialog.Value = (int)(((AlphaKnot)move_arrow).Alpha * 100.0f);
                        if (alphaPickerDialog.ShowDialog() == DialogResult.OK)
                        {
                            transFunc.BeginManipulation();
                            transFunc.KnotAlpha((AlphaKnot)move_arrow, ((float)alphaPickerDialog.Value) / 100.0f);
                            transFunc.EndManipulation();
                        }
                    }
                    if (move_arrow.GetType() == typeof(ColorKnot))
                    {
                        colorDialog.Color = ((ColorKnot)move_arrow).Color.Color;
                        if (colorDialog.ShowDialog() == DialogResult.OK)
                        {
                            transFunc.BeginManipulation();
                            transFunc.KnotColor((ColorKnot)move_arrow, colorDialog.Color);
                            transFunc.EndManipulation();
                        }
                    }
                }
            }
            //this.Invalidate();
        }

        private void colorArrowMenuDelete_Click(object sender, EventArgs e)
        {
            if (!noneSelected)
            {
                transFunc.BeginManipulation();

                transFunc.RemoveKnot(move_arrow);
                transFunc.RemoveKnot(move_arrow);

                transFunc.EndManipulation();
            }
            //this.Invalidate();
        }

        private void addColorMenu_Click(object sender, EventArgs e)
        {
            if (!noneSelected)
            {
                float pos = (float)(mov_x - left) / (width - 1);

                transFunc.BeginManipulation();

                ColorKnot k = new ColorKnot(pos, ((ColorKnot)transFunc.EvaluateColor(pos)).Color);
                transFunc.AddColorKnot(k);

                transFunc.EndManipulation();
            }
            //this.Invalidate();
        }

        private void addAlphaMenu_Click(object sender, EventArgs e)
        {
            if (!noneSelected)
            {
                float pos = (float)(mov_x - left) / (width - 1);

                transFunc.BeginManipulation();

                AlphaKnot k = new AlphaKnot(pos, ((AlphaKnot)transFunc.EvaluateAlpha(pos)).Alpha);
                transFunc.AddAlphaKnot(k);

                transFunc.EndManipulation();
            }
            //this.Invalidate();
        }

        private void RGBItem1_Click(object sender, EventArgs e)
        {
            UpdateRGBHSVMenus(false);
        }

        private void HSVItem1_Click(object sender, EventArgs e)
        {
            UpdateRGBHSVMenus(true);
        }

        private void UpdateRGBHSVMenus(bool HSV)
        {
            if (!noneSelected)
            {
                transFunc.BeginManipulation();
                transFunc.HSV = HSV;
                transFunc.EndManipulation();

                RGBItem1.Checked = RGBItem2.Checked = RGBItem3.Checked = RGBItem4.Checked = RGBItem5.Checked = !HSV;
                HSVItem1.Checked = HSVItem2.Checked = HSVItem3.Checked = HSVItem4.Checked = HSVItem5.Checked = HSV;
            }
            //this.Invalidate();
        }
        #endregion

        #region Mouse and Keyboard events
        //
        // Double click: add an alpha knot if the double click occurs in the region above the
        //  alpha bar.  Add a color knot if the double click occurs in the region below the color bar.
        //  If a knot is selected during the double click then modify it either by invoking a color 
        //   picker dialog or an alpha picker dialog.
        //
        private void GradientCtrl_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            if (!noneSelected)
            {
                int arr = arrow_h + arrow_w + 1;

                // 
                // An arrow is selected
                // Determine what type it is open the appropriate dialog to modify it
                //
                if (move_arrow != null)
                {
                    if (move_arrow.GetType() == typeof(AlphaKnot))
                    {
                        float a = ((AlphaKnot)move_arrow).Alpha * 100.0f;
                        alphaPickerDialog.Value = (int)a;
                        if (alphaPickerDialog.ShowDialog() == DialogResult.OK)
                        {
                            transFunc.BeginManipulation();
                            transFunc.KnotAlpha((AlphaKnot)move_arrow, ((float)alphaPickerDialog.Value) / 100.0f);
                            transFunc.EndManipulation();
                        }
                    }
                    if (move_arrow.GetType() == typeof(ColorKnot))
                    {
                        colorDialog.Color = ((ColorKnot)move_arrow).Color.Color;
                        if (colorDialog.ShowDialog() == DialogResult.OK)
                        {
                            transFunc.BeginManipulation();
                            transFunc.KnotColor((ColorKnot)move_arrow, colorDialog.Color);
                            transFunc.EndManipulation();
                        }
                    }
                }
                // 
                // No arrows are selected
                // Check if the click is in the correct region and add the appropriate knot
                //
                else if ((mov_x >= left) && (mov_x <= left + width - 1))
                {
                    float pos = (float)(mov_x - left) / (width - 1);

                    if ((mov_y >= 2 * height + 7 + top + arr) && (mov_y <= 2 * height + 7 + top + 2 * arr))
                    {
                        transFunc.BeginManipulation();

                        ColorKnot k = new ColorKnot(pos, ((ColorKnot)transFunc.EvaluateColor(pos)).Color);
                        transFunc.AddColorKnot(k);

                        transFunc.EndManipulation();
                    }
                    if ((mov_y >= top) && (mov_y <= top + arr))
                    {
                        transFunc.BeginManipulation();

                        AlphaKnot k = new AlphaKnot(pos, ((AlphaKnot)transFunc.EvaluateAlpha(pos)).Alpha);
                        transFunc.AddAlphaKnot(k);

                        transFunc.EndManipulation();
                    }
                }
            }
            //this.Invalidate();
        }

        //
        // Mouse down event
        //  Try to find if an arrow was clicked, if so, select it and setup the parameters
        //  needed to move the arrow
        //  Also swap in the appropriate context menu based on where the user clicked
        //
        private void GradientCtrl_MouseDown(object sender, MouseEventArgs e)
        {
            if (!noneSelected)
            {
                int arr = arrow_h + arrow_w + 1;
                int i;

                GraphicsPath path;
                ColorKnot k;

                mov_x = e.X;
                mov_y = e.Y;

                //
                // Search for whether a color arrow was hit
                // Use the graphics paths to check for collision
                //
                for (i = 0; (i < transFunc.ColorCount()) && (!moving); i++)
                {
                    k = (ColorKnot)transFunc.GetColorKnot(i);
                    path = ColorArrowPath(k);
                    if (path.IsVisible(e.X, e.Y))
                    {
                        moving = true;
                        move_arrow = k;

                        transFunc.BeginManipulation();
                    }
                }
                //
                // No hit detected? Search for whether an alpha arrow was hit
                // Use the graphics paths to check for collision
                //
                if (!moving)
                {
                    for (i = 0; (i < transFunc.AlphaCount()) && (!moving); i++)
                    {
                        k = (ColorKnot)transFunc.GetAlphaKnot(i);
                        path = AlphaArrowPath(k);
                        if (path.IsVisible(e.X, e.Y))
                        {
                            moving = true;
                            move_arrow = k;

                            transFunc.BeginManipulation();
                        }
                    }
                }
                //
                // No arrows were hit, deselect all arrows
                //
                if (!moving)
                {
                    move_arrow = null;
                }

                //
                // Update the Context Menus based on what was clicked
                //
                this.ContextMenuStrip = contextMenu1;
                if (move_arrow != null)
                {
                    if (move_arrow.GetType() == typeof(AlphaKnot))
                        this.ContextMenuStrip = alphaArrowMenu;//alphaArrowMenu.Show(this, new Point(e.X, e.Y));
                    if (move_arrow.GetType() == typeof(ColorKnot))
                        this.ContextMenuStrip = colorArrowMenu;//colorArrowMenu.Show(this, new Point(e.X, e.Y));
                }
                else if ((e.X >= left) && (e.X <= left + width - 1))
                {
                    if ((e.Y >= 2 * height + 7 + top + arr) && (e.Y <= 2 * height + 7 + top + 2 * arr))
                        this.ContextMenuStrip = newColorArrowMenu;
                    if ((e.Y >= top) && (e.Y <= top + arr))
                        this.ContextMenuStrip = newAlphaArrowMenu;
                }
            }
            //this.Invalidate();
        }

        //
        // Mouse up
        //  End the moving that was occuring
        //
        private void GradientCtrl_MouseUp(object sender, MouseEventArgs e)
        {
            if (!noneSelected)
            {
                if (e.Button == MouseButtons.Left)
                {
                    Move_Update(e);
                    mov_x = e.X;
                    mov_y = e.Y;

                    transFunc.EndManipulation();
                }
                moving = false;
            }
        }

        //
        // Mouse Move
        //  Move the selected arrow
        //
        private void GradientCtrl_MouseMove(object sender, MouseEventArgs e)
        {
            if (!noneSelected)
            {
                Move_Update(e);
            }
        }

        //
        // Update the position of an arrow that is being moved
        //
        private void Move_Update(System.Windows.Forms.MouseEventArgs e)
        {
            if (moving)
            {
                int dx = e.X - mov_x;
                mov_x = e.X;
                mov_y = e.Y;

                float new_pos = move_arrow.Pos + (float)dx / (width - 1);
                if (new_pos > 1.0f)
                { 
                    transFunc.MoveKnot(move_arrow, 1.0f);
                }
                else if (new_pos < 0.0f)
                {
                    transFunc.MoveKnot(move_arrow, 0.0f);
                }
                else
                {
                    transFunc.MoveKnot(move_arrow, new_pos);
                }

                if (e.X > width - 1 + left) { mov_x = width - 1 + left;  }
                if (e.X < left) { mov_x = left; }

                //this.Invalidate();
            }
        }

        // 
        // Key up
        //  Check for delete key hit to delete a selected knot
        //
        private void GradientCtrl_KeyUp(object sender, KeyEventArgs e)
        {
            if (!noneSelected)
            {
                if (e.KeyCode == Keys.Delete)
                {
                    transFunc.BeginManipulation();

                    transFunc.RemoveKnot(move_arrow);
                    transFunc.RemoveKnot(move_arrow);

                    transFunc.EndManipulation();
                }
            }
            //this.Invalidate();
        }
        #endregion

    }
}